/**
 * Collision.java
 *
 */

package gamepack;

import java.awt.Rectangle;
import java.awt.Polygon;
import java.util.List;
import java.util.Vector;
import java.util.Enumeration;
import java.awt.*;
import java.awt.geom.*;
import java.awt.geom.Line2D.*;
import java.awt.geom.Line2D;

/*
Note on Java Rectangle/Collision handling conventions - 

Every Image has a associated collision Rectangle. By default the size of this collision rectangle is same as the Image.

If there is a Image of size width x height pixels and has to be blit on a point x,y
then logically the collision rectangle should be.

Rectangle r = new Rectangle( x , y, width, height ); 

Now this rectangle can be used for 2 purposes - 	

1. To use the bounding rectangle in calculations for collision detection. 
	For collision detection, the co-ordinates to be used are - (r.x,r.y, r.x+r.w-1, r.y+r.h-1)
2. To draw the bounding rectangle of the image, for viewing purpose. Eg. - 
	Similarly to draw the bounding rectangle on screen we use drawRect( r.x,r.y,r.w-1,r.h-1 ); 

You might wonder why r.w-1 is used. One thing about Rectangle to be remembered is that
Java Rectangle (x,y,w,h) when drawn on screen using drawRect(x,y,w,h) has a width 'w+1'. 
Hence the r.w-1 convention while both drawing as well as doing collision testing.

Now the above convention of using rw.-1 causes problems when we use it with Java's Rectangle.intersect()
since 
r1.intersects( r2.x,r2.y,r2.width-1,r2.height-1 ) != r2.intersects( r1.x,r1.y,r1.width-1,r1.height-1 );

This means we will have to use our own version of rectangle intersection!

Behaviour of Java Polygon.intersect()
	If p is a polygon and r is a Rectangle. such that p touches r.x,r.y,r.w-1,r.h-1 then
	p.intersect(r.x,r.y,r.w-1,r.h-1) will return false;
	The intersect returns true only if the rectangle gets within the polygon.
	
This means that we will have to use our own verion of Polygon Rectangle collsion.

*/

public class Collision {

    public static Collidable createCollidable(Rectangle r) {
           return new CollidableImpl(r);
    }

    public static Collidable createCollidable(Rectangle r,int dx,int dy) {
           return new CollidableImpl(r,dx,dy);
    }

    public static Collidable createCollidable(Collidable c) {
           return new CollidableImpl(c);
    }

    public static Collidable createCollidable(Collidable c,int dx,int dy) {
           return new CollidableImpl(c,dx,dy);
    }

   /* public static boolean between(int ax1,int ay1,int ax2,int ay2,
                                  int bx1,int by1,int bx2,int by2) {
return ( ax2 < bx1 || ax1 > bx2 || ay2 < by1 || ay1 > by2) ?  false : true;
    }*/

    // Ideally we would like to use Rectangle.intersects, but there is a problem
    // with using that (read top section) so we have written the method ourself.
	
    public static boolean between(Rectangle r1,Rectangle r2) {
//		return r1.intersects(r2 );
		int ax1,ay1,ax2,ay2,bx1,by1,bx2,by2;

		ax1 = r1.x;	ax2 = ax1 + r1.width-1; // Read secton on why Rectangle.intersect(rectangle) is not used and why width-1 is required.
		ay1 = r1.y;	ay2 = ay1 + r1.height-1;

		bx1 = r2.x;	bx2 = bx1 + r2.width-1;
		by1 = r2.y;	by2 = by1 + r2.height-1;

		boolean retVal =  ( ax2 < bx1 || ax1 > bx2 || ay2 < by1 || ay1 > by2) ?  false : true;		
		return retVal;

    }
	public static boolean between(Polygon p,Rectangle r) {
		// for all lines in polygon
				//for all lines in rectangle
					//if polygon line intersects rectangle line return true;
		if ( p == null || r == null ) return false;
			Line2D pline = new Line2D.Double();
			Line2D rline = new Line2D.Double();
		boolean retVal = false;
		
		for(int i=0;i<p.npoints;i++ ) { //  npoints(3)-1 = 2;
			if ( i == p.npoints-1 ) {
				setLine( pline, p.xpoints[i],p.ypoints[i],p.xpoints[0],p.ypoints[0]);
			} else {
				setLine( pline, p.xpoints[i],p.ypoints[i],p.xpoints[i+1],p.ypoints[i+1]);
			}
			
			setLine(rline, r.x,r.y,r.x+r.width-1,r.y);   // No r.y+r.h-1
			boolean b = between( pline , rline );
			//System.out.println( "" + i + "#1 p= "+ lineToString(pline) + " r=" + lineToString(rline) + " ret" + b);
			if ( b ) {
				return true;
			}
			
			setLine(rline , r.x,r.y+r.height-1,r.x+r.width-1,r.y+r.height-1);  // No r.y here
			b = between( pline , rline );
			//System.out.println( "" + i + "#2 p= "+ lineToString(pline) + " r=" + lineToString(rline) + " ret" + b);
			if ( b ) {
				return true;
			}

			setLine( rline, r.x,r.y,r.x,r.y+r.height-1); // No r.x+r.w-1
			b = between( pline , rline );
			//System.out.println( "" + i + "#3 p= "+ lineToString(pline) + " r=" + lineToString(rline) + " ret" + b);
			if ( b ) {
				return true;
			}

			setLine( rline, r.x+r.width-1,r.y,r.x+r.width-1,r.y+r.height-1);  // No r.x
			b = between( pline , rline );
			//System.out.println( "" + i + "#4 p= "+ lineToString(pline) + " r=" + lineToString(rline) + " ret" + b);
			if ( b )  {
				return true;
			}			
		}		
		return retVal;
	}
	public static boolean between(Polygon p1,Polygon p2) {
		// for all lines in polygon1
				//for all lines in polygon2
					//if polygon1 line intersects polygon2 line return true;
		if ( p1 == null || p2 == null ) return false;
			Line2D p1line = new Line2D.Double();
			Line2D p2line = new Line2D.Double();
		boolean retVal = false;
		
		for(int i=0;i<p1.npoints;i++ ) { //  npoints(3)-1 = 2;
			if ( i == p1.npoints-1 ) {
				setLine( p1line, p1.xpoints[i],p1.ypoints[i],p1.xpoints[0],p1.ypoints[0]);
			} else {
				setLine( p1line, p1.xpoints[i],p1.ypoints[i],p1.xpoints[i+1],p1.ypoints[i+1]);
			}
			for( int j=0;j<p2.npoints;j++ ) {
				if ( j == p2.npoints-1 ) {
					setLine( p2line, p2.xpoints[j],p2.ypoints[j],p2.xpoints[0],p2.ypoints[0]);
				} else {
					setLine( p2line, p2.xpoints[j],p2.ypoints[j],p2.xpoints[j+1],p2.ypoints[j+1]);
				}			
				boolean b = between ( p1line, p2line );
				//System.out.println("p1line Vs p2line Collision = " + b);
				if( b ) { return true; }
			}
		}		
		return retVal;
	}
	
	
	public static String lineToString(Line2D line) {
		String s = "L(" +line.getX1()+","+ line.getY1() + "|" + line.getX2() + "," + line.getY2() + ")";
		return s;
	}
	
	private static void setLine(Line2D line, int a, int b,int c,int d) {
		line.setLine( (double) a, (double) b, (double) c, (double) d );
	}
	
	private static boolean between(Line2D a, Line2D b) {
	//return true;
		return a.intersectsLine( b );
	}

       // Collision between 2 Sprites.
    public static boolean between(Collidable s1,Collidable s2) { 
		   if( s1 == null || s2 == null ) return false;
           Rectangle r1 = s1.getBounds();
           Rectangle r2 = s2.getBounds();		   
           // return between(r1,r2);
		   boolean isboundingRectCollision = between ( r1,r2 );
		   if (  !isboundingRectCollision ) return false; // Good we can return, Life is easy.
		   boolean b1 = s1.hasSubShape();                 // See if can improve or cleanup below code.
		   
		   boolean b2 = s2.hasSubShape();
			if( b1 == false || b2 == false ) {
				//System.out.println("Atleast one of the 2 sprites does not have a Polygon " );
			    if ( b2 == false && b2 == false ) {
					return true; // this is a collision both don't have subshapes
				} else if( b1 == false && b2 == true ) {										
					boolean retVal = between( s2.getSubShape(), r1 );
					//System.out.println("RectVsPoly"+ Sprite.rectToString(r1) + " Vs " + 
					//					Sprite.polyToString(s2.getSubShape()) + " retVal="+ retVal );
					return retVal;
				} else if ( b1 == true && b2 == false ) {
					boolean retVal = between( s1.getSubShape(), r2 );
					//System.out.println("PolyVsRect"+ Sprite.polyToString(s1.getSubShape()) + " Vs " + Sprite.rectToString(r2) + " retVal="+ retVal );					
					return retVal;
				} 
				
		   } 
		   // both have subshapes.
			boolean retVal = between( s1.getSubShape(), s2.getSubShape() );
			//System.out.println("Polygon Vs Polygon Collision = " + retVal );
			//System.out.println("Data> " +Sprite.polyToString( s1.getSubShape() )+ " Vs " + Sprite.polyToString( s2.getSubShape() ) );
			return retVal;  
    }

    // Collision between 1 Sprite and 1 Group of Sprites.
    // Returns 0 if there are no collisions
    public static Vector<Collidable> getCollisions(Collidable c, Vector<Collidable> v) {
        Vector<Collidable> retVec = new Vector<Collidable>();
        if( v != null ) {
          for (Enumeration<Collidable> e = v.elements(); e.hasMoreElements();) {
            Collidable s = e.nextElement();
            if( Collision.between( s , c ) ) {
                retVec.add( s );
            }
          }
        }
       return retVec;
    }

    public static Vector<Collidable> getCollisions(Collidable c, Vector<Collidable>[] arrVec) {
        Vector<Collidable> retVec = new Vector<Collidable>();
        if( arrVec != null ) {
          for( int i=0;i<arrVec.length; i++ ) {
            Vector<Collidable> v = arrVec[i];
            for (Enumeration<Collidable> e = v.elements(); e.hasMoreElements();) {
                Collidable s = e.nextElement();
                if( Collision.between( s , c ) ) {
                   retVec.add( s );
                }
            }
          }
        }
        return retVec;
    }
}

// private helper class used by Collision class
class CollidableImpl implements Collidable {
      Rectangle r;
      Polygon p = null;

      CollidableImpl(Rectangle rect) { r = new Rectangle(rect);  }

      CollidableImpl(Rectangle rect,int dx,int dy) {
            r = new Rectangle(rect);
            r.translate(dx,dy);
      }

      CollidableImpl(Collidable c) {
           r = new Rectangle( c.getBounds() );
           boolean _hasSubShape = c.hasSubShape();

           if( _hasSubShape ) {
                Polygon p1 = c.getSubShape();
                p = new Polygon( p1.xpoints, p1.ypoints, p1.npoints );
           }
      }
	  
      CollidableImpl(Collidable c,int dx,int dy) {
           r = new Rectangle( c.getBounds() );
           r.translate( dx,dy );
           boolean _hasSubShape = c.hasSubShape();

           if( _hasSubShape ) {
                Polygon p1 = c.getSubShape();
                p = new Polygon( p1.xpoints, p1.ypoints, p1.npoints );
                p.translate(dx,dy);
           }
      }

      public String toString() {
             String s;
             s = " CollidabeRect(" + r.x + "," + r.y + "," + r.width + "," + r.height + ")";
             return s;
      }

       public Rectangle getBounds() {   return r;       }
       public boolean hasSubShape() { return p == null ? false : true;      }
       public Polygon getSubShape() { return p;  }
	   public void translate(int dx,int dy) {
			r.translate( dx,dy);
			if( p != null ) {
				p.translate( dx,dy );
			}
	   }
}